Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

MVVM overview

Avatar for Akifumi Fukaya Akifumi Fukaya
March 27, 2019
1.3k

MVVM overview

Avatar for Akifumi Fukaya

Akifumi Fukaya

March 27, 2019
Tweet

More Decks by Akifumi Fukaya

Transcript

  1. 自己紹介 • 名前 ◦ 深谷 哲史(ふかや あきふみ) • 会社 ◦

    株式会社メルペイ(2018/06 ~ ) • 職種 ◦ ソフトウェアエンジニア( iOS) • アカウント ◦ Twitter: @akifumifukaya ◦ Facebook: Akifumi Fukaya ◦ Github: akifumi 2
  2. About MVVM • ソフトウェアアーキテクチャパターンのひとつ • Model, View, ViewModel に分ける ◦

    Model ▪ データの管理 ▪ ドメインモデル ▪ ビジネスロジック ◦ View ▪ ユーザーインターフェイス ◦ ViewModel ▪ View と Model をつなぐ • merpay-ios-sdk では、MVVMを採用している ◦ ※ merpay-ios-sdk は、メルカリ内のメルペイの機能を提供しているもの
  3. Interface of ViewModel • ViewModelInputs ◦ View → ViewModel への入力を定義

    • ViewModelOutputs ◦ ViewModel → View への出力を定義 • ViewModelType ◦ ViewModelのインターフェイス定義
  4. ListViewModelOutputs protocol ListViewModelOutputs: class { typealias Section = ListViewModel.Section typealias

    Alert = ListViewModel.Alert var sections: [Section] { get } var reloadData: (() -> Void)? { get set } var showAlert: ((Alert) -> Void)? { get set } }
  5. ListViewModelType protocol ListViewModelInputs { func viewDidLoad() func tableViewDidSelectRow(section: Int, row:

    Int) } protocol ListViewModelOutputs: class { typealias Section = ListViewModel.Section typealias Alert = ListViewModel.Alert var sections: [Section] { get } var reloadData: (() -> Void)? { get set } var showAlert: ((Alert) -> Void)? { get set } } protocol ListViewModelType { var inputs: ListViewModelInputs { get } var outputs: ListViewModelOutputs { get } }
  6. ListViewModel final class ListViewModel: ListViewModelType, ListViewModelInputs, ListViewModelOutputs { enum Section

    { case items([Item]) } struct Alert { let title: String let message: String let completion: (() -> Void)? } }
  7. ListViewModel final class ListViewModel: ListViewModelType, ListViewModelInputs, ListViewModelOutputs { … //

    MARK: - ListViewModelType var inputs: ListViewModelInputs { return self } var outputs: ListViewModelOutputs { return self } }
  8. ListViewModel final class ListViewModel: ListViewModelType, ListViewModelInputs, ListViewModelOutputs { … //

    MARK: - ListViewModelInputs func viewDidLoad() { reload() } func tableViewDidSelectRow(section: Int, row: Int) { // do-something } }
  9. ListViewModel final class ListViewModel: ListViewModelType, ListViewModelInputs, ListViewModelOutputs { … //

    MARK: - ListViewModelOutputs private(set) var sections: [Section] = [] { didSet { reloadData?() } } var reloadData: (() -> Void)? var showAlert: ((Alert) -> Void)? }
  10. ListViewController final class ListViewController: UIViewController { private var viewModel: ListViewModelType

    init(viewModel: ListViewModelType = ListViewModel()) { self.viewModel = viewModel super.init(nibName: nil, bundle: Bundle.main) } @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
  11. ListViewController final class ListViewController: UIViewController { typealias Alert = ListViewModelOutputs.Alert

    @IBOutlet private var tableView: UITableView! … override func viewDidLoad() { super.viewDidLoad() bind() viewModel.inputs.viewDidLoad() } private func bind() { viewModel.outputs.reloadData = { [weak self] in self?.tableView.reloadData() } viewModel.outputs.showAlert = { [weak self] alert in self?.showAlert(with: alert) } } private func showAlert(with alert: Alert) { UIAlertController.ok(title: alert.title, message: alert.message, in: self, completion: { _ in alert.completion?() }) } }
  12. ListViewModelTests import XCTest @testable import ViewModelSample class ListViewModelTests: XCTestCase {

    private var viewModel: ListViewModelType! override func setUp() { super.setUp() viewModel = ListViewModel() } override func tearDown() { viewModel = nil super.tearDown() } }
  13. ListViewModelTests class ListViewModelTests: XCTestCase { private var viewModel: ListViewModelType! …

    func testSections() { viewModel.inputs.viewDidLoad() XCTAssertEqual(viewModel.outputs.sections.count, 1) guard case let .items(rows) = viewModel.outputs.sections[0] else { return XCTFail() } XCTAssertEqual(rows.count, 10) XCTAssertEqual(rows[0].name, "Item 0") } }
  14. MVVM Advantages • 可読性: View, Model, ViewModel に分割する ◦ 役割を明確に分けることができる

    ◦ クラス単位のコード量が減る ◦ コードの見通してが良くなる • テスタビリティ: コードの品質が向上する ◦ ViewModelにロジックを分割し、ロジックに対してテストを書くこと容易になる ◦ • 拡張性 ◦ ViewModelを分割し、ViewModelType/ViewModelInputs/ViewModelOutputsとインターフェイスを 設けていることにより、 ViewModelの入れ替えが容易